﻿#if UNITY_EDITOR
#define INNER_ASSET
using UnityEditor;
#endif

using UnityEngine;
using System.Collections.Generic;
using Devil;
using System.Collections;
using System.Threading;

namespace GameToolkit
{
    public enum ELoadResult : byte
    {
        Unload,
        Loading,
        Success,
        Faild,
    }

    public interface ILoadedAssetProcessor
    {
        void ProcessLoadedAsset(IAssetPtr asset);
    }

    // 资源重定向
    public interface IAssetRedirect
    {
        string RedirectAssetPath<T>(string assetPath) where T : Object;
    }

    /*
     * 因为设计所有的资源加载都为异步方式,
     * 建议仅在编辑器模式以及存在资源缓存的情况下使用同步的加载方法。
     */
    public abstract class AssetsUtil : IValidator//: IAsyncTask
    {
        public enum UnloadAction
        {
            None,
            Unused,
            Cleanup,
            ForceUnload,
        }

        internal static bool mIsBuildingAsset;
        public static bool IsBuildingAsset => mIsBuildingAsset;
#if UNITY_EDITOR
        public static string AssertAsset { get; set; }
#endif

        #region instance
#if UNITY_EDITOR
        static AssetBundleVariant sVariantsForEditor;
        public static AssetBundleVariant VariantsForEditor
        {
            get
            {
                if (sVariantsForEditor == null)
                    sVariantsForEditor = new AssetBundleVariant(null);
                return sVariantsForEditor;
            }
        }

        [InitializeOnLoadMethodParallel(IsOnMainThread = true, ExecutionOrder = InitializeOrder.First)]
        internal static IEnumerator InitializeUtilInstance()
        {
            if (sInstance == null || !Application.isPlaying)
            {
#if INNER_ASSET
                var inst = new InnerAssetsUtil();
#else
                var inst = new AssetBundleUtil(AssetSetting.DefaultSetting);
#endif
                SetInstance(inst);
            }
            sVariantsForEditor?.Dispose();
            sVariantsForEditor = new AssetBundleVariant(null);
            yield return ParallelUtils.Schedule(() => sVariantsForEditor.Initialize(), false);
        }
#endif

        private static AssetsUtil sInstance;
        public static AssetsUtil UtilInstance
        {
            get
            {
#if UNITY_EDITOR
                if (sInstance == null)
                    SetInstance(new InnerAssetsUtil());
#endif
                return sInstance;
            }
        }

        public static void SetInstance(AssetsUtil instance)
        {
            if (instance != null && sInstance != instance)
            {
                Debug.LogFormat("Set AssetsUtil as \"{0}\"", instance.GetType().Name);
                if (sInstance != null)
                {
                    sInstance.OnDestroy();
                }
                sInstance = instance;
                sInstance.OnBecomeActive();
            }
        }

        public static IAssetRedirect AssetRedirect
        {
            get
            {
                if (sInstance == null)
                    return null;
                else
                    return sInstance.mRedirect;
            }
        }

        #endregion

        #region asset operate

        protected virtual void OnBecomeActive() { }

        public static T GetAsset<T>(string assetPath) where T : Object
        {
#if UNITY_EDITOR
            Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !assetPath.ContainsIgnoreCase(AssertAsset), "sync load asset: {0}", assetPath);
#endif
            var util = UtilInstance;
            if (util == null)
                return null;
            else if (util.mRedirect == null)
                return util.LoadAsset<T>(assetPath);
            else
                return util.LoadAsset<T>(util.mRedirect.RedirectAssetPath<T>(assetPath));
        }

        public static IAssetCache<T> GetAssetAsync<T>(string assetPath) where T : Object
        {
#if UNITY_EDITOR
            Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !assetPath.ContainsIgnoreCase(AssertAsset), "async load asset: {0}", assetPath);
#endif
            var util = UtilInstance;
            if (util == null)
                return null;
            else if (util.mRedirect == null)
                return util.LoadAssetAsync<T>(assetPath);
            else
                return util.LoadAssetAsync<T>(util.mRedirect.RedirectAssetPath<T>(assetPath));
        }

        public static IAssetCache<T> GetAssetAsync<T>(string assetPath, AssetHandler<T> handler, ErrorHandler errHandler = null) where T : Object
        {
#if UNITY_EDITOR
            Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !assetPath.ContainsIgnoreCase(AssertAsset), "async load asset: {0}", assetPath);
#endif
            var util = UtilInstance;
            if (util == null)
                return null;
            var asset = util.LoadAssetAsync<T>(assetPath);
            if (asset != null)
                asset.Regist(handler, errHandler);
            else if (errHandler != null)
                errHandler(null, string.Format("Can't load asset \"{0}\"", assetPath));
            return asset;
        }

        public static IAssetPtr GetAllAssetsAsync<T>(string abname, string pattern, AssetHandler<T> handler = null,
            AssetHandler<AssetBundle> finalHandler = null, ErrorHandler errorhandler = null) where T : Object
        {
            return UtilInstance?.LoadAllAssets<T>(abname, pattern, handler, finalHandler, errorhandler);
        }

        public static void AbortLoadAssetAsync<T>(IAssetPtr id, AssetHandler<T> handler, ErrorHandler errorhandler = null) where T : Object
        {
            var cache = id as IAssetCache<T>;
            if (cache != null)
                cache.Unregist(handler, errorhandler);
        }

        public static void ReleaseUnsuedAssets()
        {
            if (sInstance != null)
            {
                sInstance.UnloadUnusedAsset(UnloadAction.Cleanup);
            }
        }

        public static bool IsUnloadingAssets { get { return sInstance != null && sInstance.CurrentUnloadAction != UnloadAction.None; } }

        public static void Destroy()
        {
            if (sInstance != null)
            {
                sInstance.OnDestroy();
                sInstance = null;
            }
        }

        public static bool HasAsset(string assetPath)
        {
            if (sInstance != null)
            {
                return sInstance.IsAssetExists(assetPath);
            }
            return false;
        }

        #endregion

        #region implement

        List<ILoadedAssetProcessor> mProcessors;
        IAssetRedirect mRedirect;
        AssetBundleVariant mVariants;
        bool mDestroied;
        public AssetBundleVariant Variants => mVariants;
        public bool IsValid => !mDestroied;
        //public bool IsAssetsPipelinePaused { get; set; }
        protected AssetsUtil(AssetBundleVariant variants)
        {
            mProcessors = new List<ILoadedAssetProcessor>();
            var self = this as ILoadedAssetProcessor;
            if (self != null)
                mProcessors.Add(self);
            if (variants == null)
                mVariants = new AssetBundleVariant(null);
            else
                mVariants = variants;
        }
        //public void StartAsyncTask() { }
        //public virtual void AbortAsyncTask()
        //{
        //    if(!IsAsyncDone)
        //        RTLog.LogError(LogCat.Asset, "Invoke static method \"AssetsUtil.AbortLoadAssetAsync<T>()\" instead of Abort().");
        //}
        internal abstract ParallelDispatcher.Handler ClearVariant(SliceList<IAssetVariantGroup> groups);
        public abstract UnloadAction CurrentUnloadAction { get; }
        public abstract T LoadAsset<T>(string assetPath) where T : Object;
        public virtual IYieldInstruction GetLoadInstruction() { return null; }
        protected abstract void UnloadUnusedAsset(UnloadAction action);
        protected virtual void OnDestroy()
        {
            mDestroied = true;
            var variant = Interlocked.Exchange(ref mVariants, null);
            if(variant != null)
            {
                variant.Dispose();
            }
        }

        protected void OnLoaded(IAssetPtr asset)
        {
            for (int i = 0; i < mProcessors.Count; i++)
            {
                try
                {
                    mProcessors[i].ProcessLoadedAsset(asset);
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
            }
        }
        public void AddProcessor(ILoadedAssetProcessor proc)
        {
            if (proc != null && !mProcessors.Contains(proc))
            {
                OnAddProcessor(proc);
            }
        }

        public void RemoveProcessor(ILoadedAssetProcessor proc)
        {
            if (proc != null)
            {
                OnRemoveProcessor(proc);
            }
        }
        public void SetAssetRedirect(IAssetRedirect redirect)
        {
            mRedirect = redirect;
        }
        protected virtual void OnAddProcessor(ILoadedAssetProcessor proc)
        {
            mProcessors.Add(proc);
        }

        protected virtual void OnRemoveProcessor(ILoadedAssetProcessor proc)
        {
            mProcessors.Remove(proc);
        }

        public abstract IAssetCache<T> LoadAssetAsync<T>(string path) where T : Object;
        //public abstract void AbortAsyncTask<T>(IAssetPtr asset, AssetHandler<T> handler, ErrorHandler errorhandler) where T : Object;
        public abstract IAssetPtr LoadAllAssets<T>(string abname, string regex, AssetHandler<T> handler, AssetHandler<AssetBundle> finalHandler,
            ErrorHandler errorHandler) where T : Object;
        public abstract bool IsAssetExists(string asset);
        public abstract ParallelDispatcher.Handler FindAllAssets(string abname, string regex, ICollection<string> results);

        //public abstract bool IsAsyncDone { get; }
        //public abstract float Progress { get; }
        #endregion
    }
}